package cn.yzj.utils;

import cn.yzj.common.CommonSymbol;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * 数据处理工具类
 *
 * @author gzkemays
 * @since 2021/1/28 16:41
 */
public class DataUtils {
  private static final Logger logger = LoggerFactory.getLogger(DataUtils.class);

  /**
   * 根据 key 来过滤重复数据
   *
   * <p>配合 {@link java.util.stream.Stream#filter(Predicate)} 的过滤特性，返回 {@link Boolean#TRUE} 的结果。因此做
   * {@link ConcurrentHashMap#putIfAbsent(Object, Object)} 来判断 tempSaveMap 是否有相同的键值。如果有，tempSaveMap
   * 是不会存储当前对象的并且返回 {@code true}，如果没有对应的键值则返回 {@code null}。
   *
   * @param keyExtractor lambda 函数式实现接口
   * @param <T> 接受任意数据类型
   * @return {@link Boolean#TRUE} or {@link Boolean#FALSE} 搭配 filter 实现过滤
   */
  public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Map<Object, Boolean> tempSaveMap = new ConcurrentHashMap<>();
    return obj -> Objects.isNull(tempSaveMap.putIfAbsent(keyExtractor.apply(obj), Boolean.TRUE));
  }

  /**
   * 将 map 转换为 pojo 对象
   *
   * @param source map
   * @param clazz pojo 类型
   * @param <T> 泛型
   * @return pojo
   */
  public static <T> T getObjectFromMap(Map<String, Object> source, Class<T> clazz) {
    try {
      T target = clazz.newInstance();
      Field[] declaredFields = clazz.getDeclaredFields();
      for (Field field : declaredFields) {
        field.setAccessible(true);
        String name = field.getName();
        if (Objects.nonNull(source.get(name))) {
          field.set(target, source.get(field.getName()));
        }
      }
      return target;
    } catch (InstantiationException e) {
      logger.error("无参构造失败");
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      logger.error("实体类：" + clazz + "构造或传参出现异常");
      e.printStackTrace();
    }
    return null;
  }

  /**
   * 将 pojo 转换为 map
   *
   * @param obj pojo
   * @return map
   */
  public static Map<String, Object> getMapFromObject(Object obj) {
    Class<?> clazz = obj.getClass();
    Field[] declaredFields = clazz.getDeclaredFields();
    Map<String, Object> map = new HashMap<>(declaredFields.length);
    for (Field declaredField : declaredFields) {
      declaredField.setAccessible(true);
      try {
        map.put(declaredField.getName(), declaredField.get(obj));
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      }
    }
    return map;
  }

  /**
   * 获取 JSON 格式的数据对象
   *
   * @param jsonStr json 字符串
   * @param spiltStr 分割符
   * @return JSON 格式的数据对象
   */
  public static String getColData(String jsonStr, String spiltStr) {
    JSONObject jsonObject = JSON.parseObject(jsonStr);
    while (spiltStr.contains(CommonSymbol.FULL_STOP)) {
      int index = spiltStr.indexOf(".");
      String data = spiltStr.substring(0, index);
      spiltStr = spiltStr.substring(index).replaceFirst(".", "");
      jsonObject = (JSONObject) jsonObject.get(data);
    }

    return jsonObject.getString(spiltStr);
  }

  /**
   * 截取重定向的数据
   *
   * @param uri(a=b&c=d&e=f)
   * @return {a:b,c:d,e:f}
   */
  public static Map<String, String> getQueryParam(String uri) {
    Map<String, String> map = new HashMap<>(0);
    String[] strArr;
    strArr = uri.substring(uri.indexOf("&")).replaceFirst("&", "").split("&");
    for (String str : strArr) {
      String[] tempArr;
      tempArr = str.split("=");
      if (tempArr.length > 1) {
        map.put(tempArr[0], tempArr[1]);
      }
    }
    return map;
  }

  /**
   * 截取 Url Query
   *
   * @param uri 含参 url {@code url=http://www.xxx.com?a=b&c=d ..}
   * @return query 键值对 {@code { a: b, c: d } }
   */
  public static Map<String, String> getQueryByUrl(String uri) {
    Map<String, String> map = new HashMap<>(0);
    String[] strArr;
    strArr = uri.substring(uri.indexOf("?") + 1).split("&");
    for (String str : strArr) {
      String[] tempArr;
      tempArr = str.split("=");
      if (tempArr.length > 1) {
        map.put(tempArr[0], tempArr[1]);
      }
    }
    return map;
  }

  /** 对象拷贝（ null 不拷贝） */
  public static Object copyProperties(Object source, Object target) {
    Class<?> targetClass = target.getClass();
    Class<?> sourceClass = source.getClass();
    for (Field sourceField : sourceClass.getDeclaredFields()) {
      // 获取字段的最高权限
      try {
        sourceField.setAccessible(true);
        String key = sourceField.getName();
        Object value = sourceField.get(source);
        Field targetField = targetClass.getDeclaredField(key);
        targetField.setAccessible(true);
        if (Objects.nonNull(value)) {
          targetField.set(target, value);
        } else {
          targetField.set(target, targetField.get(target));
        }
      } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
      }
    }
    return target;
  }

  /**
   * 截取某个标记之前的字符串
   *
   * @param str
   * @param tag
   * @return
   */
  public static String substrBeforeOfTag(String str, String tag) {
    return str.substring(0, str.indexOf(tag));
  }

  public static String substrBeforeLastOfTag(String str, String tag) {
    return str.substring(0, str.lastIndexOf(tag));
  }

  /**
   * 截取某个标记之后的字符串
   *
   * @param str
   * @param tag
   * @return
   */
  public static String substrAfterOfTag(String str, String tag) {
    return str.substring(str.indexOf(tag) + 1);
  }

  /**
   * 截取两个标记之间的字符串,tag 默认为第一个
   *
   * @param str
   * @param tag
   * @param tag2
   * @return
   */
  public static String substrBetweenOfTag(String str, String tag, String tag2) {
    return str.substring(str.indexOf(tag) + 1, str.indexOf(tag2));
  }

  /**
   * 截取两个标记之间的字符串,首标记从后开始查询
   *
   * @param str
   * @param tag
   * @param tag2
   * @return
   */
  public static String substrBetweenOfLastOfFirstTag(String str, String tag, String tag2) {
    return str.substring(str.lastIndexOf(tag) + 1, str.indexOf(tag2));
  }

  /**
   * 截取两个标记之间的字符串,后标记从后开始查询
   *
   * @param str
   * @param tag
   * @param tag2
   * @return
   */
  public static String substrBetweenOfLastOfLastTag(String str, String tag, String tag2) {
    return str.substring(str.indexOf(tag) + 1, str.lastIndexOf(tag2));
  }

  /**
   * 获取前置包夹的字符串
   *
   * @param str 字符串
   * @param first 前置1
   * @param first2 前置2
   * @return 字符串截取结果
   */
  public static String substrBetweenOfFirstAndFirst(String str, String first, String first2) {
    int firstIndex = str.indexOf(first), lastIndex = str.indexOf(first2);
    if (firstIndex >= lastIndex) {
      String tmp = substrBeforeOfTag(str, first2);
      firstIndex = tmp.lastIndexOf(first);
    }
    return betweenSubstr(str, firstIndex, lastIndex);
  }

  /**
   * 获取前后包夹至的字符串
   *
   * <p>如果 firstIndex == lastIndex 那么返回的是其后续字符串
   *
   * @param str 字符串
   * @param first 前缀
   * @param last 后缀
   * @return 其中的字符串
   */
  public static String substrBetweenOfFirstAndLast(String str, String first, String last) {
    return betweenSubstr(str, str.indexOf(first), str.lastIndexOf(last));
  }

  /**
   * 双后包夹字符串
   *
   * @param str 字符串
   * @param last 后续 1
   * @param last2 后续 2
   * @return 其中的字符串
   */
  public static String substrBetweenOfLastAndLast(String str, String last, String last2) {
    int firstIndex = str.lastIndexOf(last), lastIndex = str.lastIndexOf(last2);
    if (firstIndex >= lastIndex) {
      String tmp = substrBeforeOfTag(str, last2);
      firstIndex = tmp.lastIndexOf(last);
    }
    return betweenSubstr(str, firstIndex, lastIndex);
  }

  private static String betweenSubstr(String str, int first, int last) {
    if (first < last) {
      return str.substring(first + 1, last);
    }
    return str.substring(last + 1);
  }

  /**
   * 截取两个标记之间的字符串，一致往后开始查询
   *
   * @param str
   * @param tag
   * @param tag2
   * @return
   * @deprecated {@link #substrBetweenOfFirstAndLast(String, String, String)}
   */
  public static String substrBetweenOfFirstOfLastTag(String str, String tag, String tag2) {
    int lastIndex = str.indexOf(tag);
    int firstIndex = str.lastIndexOf(tag2);
    if (lastIndex == firstIndex) {
      firstIndex = substrBeforeOfTag(str, tag).lastIndexOf(tag2) + 1;
    }
    return str.substring(firstIndex, lastIndex);
  }

  /**
   * @deprecated {@link #substrBetweenOfLastAndLast(String, String, String)}
   * @param str
   * @param tag
   * @param tag2
   * @return
   */
  public static String substrBetweenOfLastOfDoubleTag(String str, String tag, String tag2) {
    int lastIndex = str.lastIndexOf(tag2);
    int firstIndex = str.lastIndexOf(tag);
    if (lastIndex == firstIndex) {
      firstIndex = substrBeforeLastOfTag(str, tag2).lastIndexOf(tag) + 1;
    }
    return str.substring(firstIndex, lastIndex);
  }

  /**
   * 获取最后标记的前面字符串
   *
   * @param str
   * @param tag
   * @return
   */
  public static String substrAfterOfLastTag(String str, String tag) {
    return str.substring(str.lastIndexOf(tag) + 1);
  }

  /**
   * 替换最后一个
   *
   * @param str
   * @param oldTag
   * @param newTag
   * @return
   */
  public static String replaceLast(String str, String oldTag, String newTag) {
    return str.replaceFirst("(?s)" + oldTag + "(?!.*?" + oldTag + ")", newTag);
  }

  public static String replaceLastToEmpty(String str, String oldTag) {
    return str.replaceFirst("(?s)" + oldTag + "(?!.*?" + oldTag + ")", "");
  }

  /**
   * str 的 xml 格式解析
   *
   * @param str
   * @return
   * @throws DocumentException
   */
  public static Map<String, Object> strParseXml(String str) throws DocumentException {
    Map<String, Object> map = new HashMap<>(0);
    if (!str.startsWith("<xml")) {
      str = "<xml>" + str;
    }
    if (!str.endsWith("xml/>")) {
      str = str + "</xml>";
    }
    SAXReader reader = new SAXReader();
    Document document = reader.read(new ByteArrayInputStream(str.getBytes()));
    parsing(document, map);
    return map;
  }

  public static Map<String, Object> inputStreamParseXml(InputStream is) throws DocumentException {
    Map<String, Object> map = new HashMap<>(0);
    SAXReader reader = new SAXReader();
    Document document = reader.read(is);
    parsing(document, map);
    return map;
  }

  private static void parsing(Document document, Map<String, Object> map) {
    Element root = document.getRootElement();
    List<Element> elementList = root.elements();
    for (Element e : elementList) {
      map.put(e.getName(), e.getText());
      returnParseXml(map, e, e.getName());
    }
  }

  private static void returnParseXml(Map<String, Object> map, Element element, String parentKey) {
    List<Element> elements = element.elements();
    Map<String, Object> tempMap = new HashMap<>(0);
    if (!elements.isEmpty()) {
      for (Element e : elements) {
        tempMap.put(e.getName(), e.getText());
        returnParseXml(tempMap, e, e.getName());
      }
      map.put(parentKey, tempMap);
    }
  }

  /**
   * 获取组合所有可能性
   *
   * @param sessions
   * @return
   */
  public static <T> List<List<T>> analysisCombination(List<T> sessions) {
    List<List<T>> result = new ArrayList<>();
    peelOffList(sessions, 0, new ArrayList<>(), result);
    return result;
  }

  /**
   * 递归回溯找组合
   *
   * @param sessions
   * @param start
   * @param temp
   * @param result
   */
  private static <T> void peelOffList(
      List<T> sessions, int start, List<T> temp, List<List<T>> result) {
    if (!temp.isEmpty()) {
      result.add(new ArrayList<>(temp));
    }
    for (int i = start; i < sessions.size(); i++) {
      temp.add(sessions.get(i));
      peelOffList(sessions, i + 1, temp, result);
      // 回溯
      temp.remove(temp.size() - 1);
    }
  }

  public static void main(String[] args) {
    String k = "2d151e3b9164b4bd2489749acd55833a-31->20_gzkemays#common$loli古典旗袍.zip";

    int chunks;
    String md5 = "", username = "", path = "", filename = "";
    md5 = DataUtils.substrBeforeOfTag(k, "-");
    chunks = Integer.parseInt(DataUtils.substrBetweenOfFirstAndFirst(k, "-", "->"));
    username = DataUtils.substrBetweenOfFirstAndFirst(k, "_", "#");
    path = DataUtils.substrBetweenOfFirstAndFirst(k, "#", "$");
    filename = DataUtils.substrAfterOfLastTag(k, "$");

    System.out.println("md5=" + md5);
    System.out.println("chunks=" + chunks);
    System.out.println("username=" + username);
    System.out.println("path=" + path);
    System.out.println("filename=" + filename);
    System.out.println(substrBetweenOfFirstAndLast("#a", "#", "#"));
  }
}
